package com.wrbug.mmkw.compile.generator

import com.squareup.javapoet.ClassName
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import com.sun.tools.javac.code.Attribute
import com.sun.tools.javac.code.Symbol
import com.sun.tools.javac.code.Type
import com.sun.tools.javac.util.List
import com.wrbug.mmkw.annotation.KwGet
import com.wrbug.mmkw.annotation.KwInterface
import com.wrbug.mmkw.compile.MethodParser
import com.wrbug.mmkw.compile.MmkvClassName
import com.wrbug.mmkw.compile.MmkvMethod
import com.wrbug.mmkw.compile.MmkvType
import javax.annotation.processing.Filer
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement

/**
 *   ImplGenerator.java
 *   author: wrbug
 *   date: 2019-09-23 19:34
 *
 */

class ImplGenerator(mFiler: Filer) : ElementGenerator(mFiler) {
    companion object {
        const val INSTANCE_FIELD = "instance"
        const val MMKV_FIELD = "kv"
        const val GET_INSTANCE_METHOD = "getInstance"
        const val HOLDER_CLASS = "Holder"
        val baseMmkvTaskerImpl = ClassName.get("com.wrbug.mmkw", "BaseMmkvTaskerImpl")
    }

    override fun onCreateTypeSpec(element: TypeElement, className: ClassName): TypeSpec {
        val mmkvId = element.getAnnotation(KwInterface::class.java).id
        val multiProcess = element.getAnnotation(KwInterface::class.java).multiProcess
        val thisClassName = ClassName.get(className.packageName(), className.simpleName() + "Impl")
        val builder = TypeSpec.classBuilder(className.simpleName() + "Impl")
            .addModifiers(Modifier.PUBLIC,Modifier.FINAL)
            .addField(
                thisClassName,
                INSTANCE_FIELD,
                Modifier.PRIVATE,
                Modifier.STATIC
            )
            .superclass(baseMmkvTaskerImpl)
            .addType(getInnerStaticInstanceClass(thisClassName))
            .addMethod(buildGetInstanceMethod(thisClassName))
            .addMethod(buildConstructor(mmkvId, multiProcess))
            .addSuperinterface(className)
        element.enclosedElements.filter { it is Symbol.MethodSymbol }.forEach {
            builder.addMethod(buildImplMethod(it as Symbol.MethodSymbol))
        }
        return builder.build()
    }

    private fun buildImplMethod(methodSymbol: Symbol.MethodSymbol): MethodSpec? {
        val methodInfo = MethodParser.parse(methodSymbol)
        val builder = MethodSpec.methodBuilder(methodInfo.originMethodName)
            .addAnnotation(Override::class.java)
            .addModifiers(Modifier.PUBLIC)
        if (methodInfo.mmkvType == MmkvType.GET) {
            builder.returns(methodInfo.returnType)
            builder.addStatement(
                "return \$L.\$L(\"\$L\")",
                MMKV_FIELD,
                methodInfo.mmkvReturnType?.mmkvMethod,
                methodInfo.key
            )
        } else {
            if (methodInfo.mmkvType == MmkvType.SET) {
                builder.addParameter(methodInfo.parameter, "data", Modifier.FINAL)
                builder.addStatement("\$L.\$L(\"\$L\",\$L)", MMKV_FIELD, MmkvMethod.ENCODE, methodInfo.key, "data")
            } else if (methodInfo.mmkvType == MmkvType.REMOVE) {
                builder.addStatement("\$L.\$L(\"\$L\")", MMKV_FIELD, MmkvMethod.REMOVE_VALUE_FOR_KEY, methodInfo.key)
            }
        }
        return builder.build()
    }


    private fun getInnerStaticInstanceClass(thisClassName: ClassName) =
        TypeSpec.classBuilder(HOLDER_CLASS)
            .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
            .addField(
                FieldSpec.builder(
                    thisClassName,
                    INSTANCE_FIELD,
                    Modifier.PRIVATE,
                    Modifier.STATIC,
                    Modifier.FINAL
                ).initializer("new \$T()", thisClassName).build()
            )
            .build()

    private fun buildGetInstanceMethod(thisClassName: ClassName) =
        MethodSpec.methodBuilder(GET_INSTANCE_METHOD)
            .returns(thisClassName)
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
            .addStatement("return \$L.\$L", HOLDER_CLASS, INSTANCE_FIELD)
            .build()


    private fun buildConstructor(mmkvId: String, multiProcess: Boolean): MethodSpec? {
        val builder = MethodSpec.constructorBuilder()
        builder.addModifiers(Modifier.PRIVATE)
        if (mmkvId.isEmpty()) {
            builder.addStatement("super(\$T.\$L())", MmkvClassName.MMKV, MmkvMethod.DEFAULT_MMKV)
        } else {
            if (multiProcess) {
                builder.addStatement(
                    "super(\$T.\$L(\"\$L\",MMKV.MULTI_PROCESS_MODE))",
                    MmkvClassName.MMKV,
                    MmkvMethod.MMKV_WITH_ID,
                    mmkvId
                )
            } else {
                builder.addStatement(
                    "super(\$T.\$L(\"\$L\"))",
                    MmkvClassName.MMKV,
                    MmkvMethod.MMKV_WITH_ID,
                    mmkvId
                )
            }
        }
        return builder.build()
    }
}